/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package algos;

import java.util.Random;
import model.Algorithm;

/**
 *
 * @author Administrator
 */
public class BlackScholesModel extends Algorithm {

    private static final OptionType[] optionTypes = new OptionType[]{OptionType.EUROPEAN_CALL, OptionType.EUROPEAN_PUT, OptionType.AMERICAN_CALL};
    private static final String algoName = "Black Scholes";

    public BlackScholesModel() {
        super(algoName, optionTypes, new double[]{0.0, 0.0});
    }

    public BlackScholesModel(String companyTicker, double currentStockPrice, double strikePrice, double term, double volatility, double riskFreeRate, double[] extraParametersInput) {
        super(algoName, companyTicker, currentStockPrice, strikePrice, term, volatility, riskFreeRate, optionTypes, extraParametersInput);
    }

    @Override
    public void initExtraParameters(double[] extraParametersInput) {
    }

    // Black-Scholes call formula
    public double calculateCallPrice() {
        double d1 = (Math.log(super.getCurrentStockPrice() / super.getStrikePrice()) + (super.getRiskFreeRate() + super.getVolatility() * super.getVolatility() / 2) * super.getTerm()) / (super.getVolatility() * Math.sqrt(super.getTerm()));
        double d2 = d1 - super.getVolatility() * Math.sqrt(super.getTerm());
        return super.getCurrentStockPrice() * Gaussian.Phi(d1) - super.getStrikePrice() * Math.exp(-super.getRiskFreeRate() * super.getTerm()) * Gaussian.Phi(d2);
    }

    // Black-Scholes put formula
    public double calculatePutPrice() {
        double d1 = (Math.log(super.getCurrentStockPrice() / super.getStrikePrice()) + (super.getRiskFreeRate() + super.getVolatility() * super.getVolatility() / 2) * super.getTerm()) / (super.getVolatility() * Math.sqrt(super.getTerm()));
        double d2 = d1 - super.getVolatility() * Math.sqrt(super.getTerm());
        return super.getStrikePrice() * Math.exp(-super.getRiskFreeRate() * super.getTerm()) * Gaussian.Phi(-d2) - super.getCurrentStockPrice() * Gaussian.Phi(-d1);
    }

    private static class Gaussian {

        public static double phi(double x) {
            return Math.exp(-x * x / 2) / Math.sqrt(2 * Math.PI);
        }

        // return phi(x, mu, signma) = Gaussian pdf with mean mu and stddev sigma
        public static double phi(double x, double mu, double sigma) {
            return phi((x - mu) / sigma) / sigma;
        }

        // return Phi(z) = standard Gaussian cdf using Taylor approximation
        public static double Phi(double z) {
            if (z < -8.0) {
                return 0.0;
            }
            if (z > 8.0) {
                return 1.0;
            }
            double sum = 0.0, term = z;
            for (int i = 3; sum + term != sum; i += 2) {
                sum = sum + term;
                term = term * z * z / i;
            }
            return 0.5 + sum * phi(z);
        }

        // return Phi(z, mu, sigma) = Gaussian cdf with mean mu and stddev sigma
        public static double Phi(double z, double mu, double sigma) {
            return Phi((z - mu) / sigma);
        }

        // Compute z such that Phi(z) = y via bisection search
        public static double PhiInverse(double y) {
            return PhiInverse(y, .00000001, -8, 8);
        }

        // bisection search
        private static double PhiInverse(double y, double delta, double lo, double hi) {
            double mid = lo + (hi - lo) / 2;
            if (hi - lo < delta) {
                return mid;
            }
            if (Phi(mid) > y) {
                return PhiInverse(y, delta, lo, mid);
            } else {
                return PhiInverse(y, delta, mid, hi);
            }
        }
    }

    public static void main(String[] args) {
        Algorithm a = new BlackScholesModel("A", 50, 50, 0.4, 0.4, 0.1, new double[]{});
        System.out.println(a.calculateCallPrice());
        System.out.println(a.calculatePutPrice());
    }
}
